#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
//#include<netinet/in.h>
#include<sys/socket.h>
#include<netdb.h>
#include<sys/types.h>
#include<sys/time.h>
#include<sys/select.h>
#include<fcntl.h>
#include<signal.h>
#include<sys/signal.h>

#define PORT 9090
#define BUFF_SIZE 256
#define NAMELEN 32
#define PASSWDLEN 224
#define LocalHost   "127.0.0.1"
#define MSGLEN  1024

#define REGISTER    1
#define LOGIN       2
#define EXIT        3
#define LOGOUT      4

#define RET_REG_OK      1
#define RET_REG_FAIL    2    
#define RET_LOGIN_OK    3
#define RET_LOGIN_FAIL  4
#define RET_ERROR       5

struct user_info {
    char name[NAMELEN];
    char passwd[PASSWDLEN];
};


#define type_reg    0x01
#define type_login  0x02
#define type_logout 0x04
#define type_msg    0x08

unsigned char pkt_header;

static unsigned char buff[BUFF_SIZE];
static int op;  // global option
static int type;    // register, login, logout
static struct user_info user;
static int socketfd;
static fd_set readfds;
// screen layer
void init_screen(void);
void show_menu(int *option);
void show_result(int ret);

// modules
int mod_register(void);
int send_reg_msg(void);
int mod_login(void);
int send_login_msg(void);
int mod_logout(void);
int send_logout_msg(void);
void mod_help(void);
int check_format(char *str, char c, int len);

// socket layer
int init_socket(struct sockaddr_in *servip);
int send_message(char *buf, int size);
int recv_msg(fd_set readfds);

//main window
int chat_main(void);
void *chat_window(void *arg);
void sig_process(int arg);

// get current system time
static char *get_cur_time(char *buf, int size);

int main(void)
{
    struct sockaddr_in servip;
    struct timeval tv;
    int err, maxfd=0, servip_len = 0;
    int ret = 0, loop = 1;
    
//	FD_SET(0,&readfds);
	
	tv.tv_sec =0;
	tv.tv_usec =1000000;//100ms de chaoshi.
	
	while ( loop ) {    
        init_screen();
        FD_ZERO(&readfds);//清除一个文件描述符集
        ret = 0;
        int res = 0;
        switch ( type ) {
            case REGISTER:
                if ( !init_socket(&servip) ) {
                    FD_SET(socketfd,&readfds);
    	            if(maxfd < socketfd) 
    	                maxfd = socketfd; 
	                servip_len = sizeof(servip);
                    err = connect(socketfd,(struct sockaddr*)&servip,servip_len);
                    if(err == -1)
    	            {
    		            perror("connect");
    		            close(socketfd);
    		            return -1;
    	            }
    	            printf("success connect to %s:%d\n",
    		            (char*)inet_ntoa(servip.sin_addr),
    		            ntohs(servip.sin_port));
                    send_reg_msg();
                    err=select(maxfd+1,&readfds,NULL,NULL,NULL);
                    ret = recv_msg(readfds);
                    show_result(ret);
                    close(socketfd); 
                }
                break;
            case LOGIN:
                if ( !init_socket(&servip) ){
                    FD_SET(socketfd,&readfds);
    	            if(maxfd < socketfd) 
    	                maxfd = socketfd; 
	                servip_len = sizeof(servip);
                    err = connect(socketfd,(struct sockaddr*)&servip,servip_len);
                    if(err == -1)
    	            {
    		            perror("connect");
    		            close(socketfd);
    		            return -1;
    	            }
    	            printf("success connect to %s:%d\n",
    		            (char*)inet_ntoa(servip.sin_addr),
    		            ntohs(servip.sin_port));
                    send_login_msg();
                    err=select(maxfd+1,&readfds,NULL,NULL,NULL);
                    ret = recv_msg(readfds);
                    show_result(ret);
                    if ( ret == RET_LOGIN_OK ) {
                        res = chat_main();  // jump to main chat window
                        if ( res == EXIT ) {
                            loop = 0;
                        }
                    }
                    close(socketfd); 
                }
                break;
            case EXIT:
                loop = 0;
                break;
            default:
                break;   
        }
    }
		
	return 0;
}


void init_screen(void)
{
    int loop = 1;
    while ( loop ) {
        op = 2;
        system("reset");
        show_menu(&op);
        switch ( op ) {
            case REGISTER:
                type = mod_register(); loop = 0;
                break;
            case LOGIN:
                type = mod_login(); loop = 0;
                break;
            case EXIT:
                exit(0);
                break;
            default:
                printf("Input error! 2 seconds return\n");
                sleep(2);
                break;
        }
    }

    return ;
}

void show_menu(int *option)
{
    printf("###################### Clinet Menu ######################\n");
    printf("#                                                       #\n");
    printf("#                                                       #\n");
    printf("# Please select one option[default:login]:              #\n");
    printf("#                                                       #\n");
    printf("#        1.register                                     #\n");
    printf("#        2.login                                        #\n");
    printf("#        3.exit                                         #\n");
    printf("#                                                       #\n");
    printf("#########################################################\n");
    printf("\n\n\n\n\n>");
    fflush(stdin);      // avoid the influence of  pre-input
    scanf("%d", option);
    return;
}

int mod_register(void)
{
    int loop = 1;
    char reginfo[BUFF_SIZE];
    
    while ( loop ) {
        system("reset");
        printf("######################   Register  ######################\n");
        printf("#                                                       #\n");
        printf("#                                                       #\n");
        printf("# Please register an account like this:                 #\n");
        printf("#                                                       #\n");
        printf("#       name|passwd                                     #\n");
        printf("# For an example:                                       #\n");
        printf("#       jerry|000000                                    #\n");
        printf("#                                                       #\n");
        printf("#########################################################\n");
        printf("\n\n\n\n\n>");
        fflush(stdin);      // avoid the influence of  pre-input
        memset(reginfo, 0, BUFF_SIZE);
        scanf("%s", reginfo);   // buffer overflow problem
        loop = check_format(reginfo, '|', NAMELEN);
        if ( !loop ) {
            char *pos = 0;
            pos = strchr(reginfo, '|');
            strncpy(user.name, reginfo, pos-reginfo);
            strcpy(user.passwd, pos+1);
        }
    }
    return REGISTER;
}

int mod_login(void)
{
    int loop = 1;
    
    while ( loop ) {
        system("reset");
        
        printf("########################   Login  #######################\n");
        printf("\n\n\n\n\n>name:");
        fflush(stdin);      // avoid the influence of  pre-input
        memset(&user, 0, sizeof(user));
        scanf("%s", user.name);  
        printf(">passwd:");
        scanf("%s", user.passwd);
        if ( strlen(user.name) <= NAMELEN 
            && strlen(user.passwd) <= PASSWDLEN ) {
            printf("check OK!\n");
            loop = 0;
        }
    }
    return LOGIN;
}

int check_format(char *str, char c, int len)
{
    char *pos = 0;
    int curlen = 0;
    if ( !str ) {
        printf("Format error!\n");
        return 1;
    }

    pos = strchr(str, c);
    if ( !pos ) {
        printf("Format error!\n");
        return 1;
    }
    curlen = pos - str;

    if ( curlen > len ) {
        printf("Format error!\n");
        return 1;
    }

    return 0;
}

void show_main_screen(void)
{
    system("reset");
    printf(">");

    return ;
}

int mod_logout(void)
{
    return LOGOUT;
}


void mod_help(void)
{

}

int init_socket(struct sockaddr_in *servip)
{
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    if(socketfd == -1)
	{
		perror("socket");
		return 1;
	}
	servip->sin_family = AF_INET;//IPV4 DE DIZHI
	servip->sin_port = htons(PORT);
	inet_pton(AF_INET,LocalHost,&(servip->sin_addr));//可以识别IPV4和6
	bzero(&servip->sin_zero,8);

	return 0;
}

int send_message(char *buf, int size)
{
    int err;
    char msg[BUFF_SIZE+1];
    char time[BUFF_SIZE];
    
    get_cur_time(time, BUFF_SIZE);
	memset(msg, 0, BUFF_SIZE+1);
    sprintf(msg, "%c%s %s\n%s", type_msg, time, user.name, buf);
    err = write(socketfd, msg, strlen(msg));
	if(err <= 0)
	{
		perror("send");
		return err;
	}

	return err;
}

int send_reg_msg(void)
{
    char msg[BUFF_SIZE+1];
    int err, len;
    
    memset(msg, 0, BUFF_SIZE+1);
    sprintf(msg, "%c%s!%s", type_reg, user.name, user.passwd);
    len = strlen(msg);
    err = write(socketfd, (char *)msg, len);
    if ( err <= 0)
    {
        perror("send");
		return err;
    }
    
    return err;
}

int send_login_msg(void)
{
    char msg[BUFF_SIZE+1];
    int err, len;
    
    memset(msg, 0, BUFF_SIZE+1);
    sprintf(msg, "%c%s!%s", type_login, user.name, user.passwd);
    len = strlen(msg);
    err = write(socketfd, msg, len);
    if ( err <= 0)
    {
        perror("send");
		return err;
    }
    
    return err;
}

int send_logout_msg(void)
{
    char msg[BUFF_SIZE];
    int err, len;
    
    memset(msg, 0, BUFF_SIZE);
    sprintf(msg, "%c%s!%s", type_logout, user.name, user.passwd);
    len = strlen(msg);
    err = write(socketfd, msg, len);
    if ( err <= 0)
    {
        perror("send");
		return err;
    }
    
    return err;
}

int recv_msg(fd_set readfds)
{
    int recvlen = 0;
    char buf[BUFF_SIZE];
    
    if(FD_ISSET(socketfd,&readfds))
	{
		memset(buf, 0, BUFF_SIZE);
		recvlen = recv(socketfd, buf, BUFF_SIZE,0);
		printf("\nrecvlen=%d\n",recvlen);
		if(recvlen < 0)
		{
			perror("recv");
			return RET_ERROR;
		} 
		if(recvlen == 0)
		{
			perror("recv0");//biaoshi duandiaole .
			return RET_ERROR;
		}
		if(recvlen > 0)
    	{
    	    printf("%d\n", recvlen);
    		buff[recvlen] = '\0';
    		printf("==>%s\n",buf);//会多两个字节/r/n
    		return buf[0];
   		}
	    
	}
	
	return RET_ERROR;
}

void show_result(int ret)
{
    system("reset");
    switch ( ret ) {
        case RET_REG_OK:
            printf("Register OK!\n");
            break;
        case RET_REG_FAIL:
            printf("Register Fail\n");
            break;
        case RET_LOGIN_OK:
            printf("Login OK!\n");
            break;
        case RET_LOGIN_FAIL:
            printf("Login Fail!\n");
            break;
        default:
            printf("error!\n");
            break;
    } 
    sleep(2);
    return ;
}

int chat_main(void)
{
    pthread_t ttid;
    char buf[BUFF_SIZE];
    int num = 0;
    fd_set rdfds;
    
    FD_ZERO(&rdfds);
    FD_SET(socketfd, &rdfds);
    
    signal(SIGINT, sig_process);
    pthread_create(&ttid, NULL, chat_window, NULL);
    
    while ( 1 ) {
        memset(buf, 0, BUFF_SIZE);
        select(socketfd+1, &rdfds, NULL, NULL, NULL);
        num = recv(socketfd, buf, BUFF_SIZE, 0);
//		printf("num=%d\n", num);
		if ( num == 0 ) {
		    printf("server close\n");
		    sleep(1);
		    return EXIT;
		}
        if ( num < 0 ) {
            printf("error recv!\n");
            continue;
        }
        if ( num > 20 ) {
            char *p = 0;
            int len = 0;
            p = strchr(buf+21, '\n');
            if ( p != NULL ) {
                len = p - buf - 21;
                if ( strncmp(buf+21, user.name, len) == 0 ) {
           //         printf("ignore self message!\n");
                    printf("=");
                    continue;   // the message from self, and ignore it
                }
            }
        }
        printf("\n== %s", buf);
        printf("\n=");
    }
    
}



void sig_process(int arg) {
    char buf[BUFF_SIZE];
    memset(buf, 0, BUFF_SIZE);
    buf[0] = type_logout;
    send_logout_msg();
    printf("exit!\n");
    close(socketfd);
    exit(1);
}

void *chat_window(void *arg)
{
    int ret = 0;
    char buffer[BUFF_SIZE];
    char timebuf[BUFF_SIZE];
//    char tmp[BUFF_SIZE];
    FILE *fp = 0;
    fp = fopen("/dev/fd/0", "r");
    if ( !fp ) {
        printf("open stdin error! exit\n");
        pthread_exit(NULL);
    }
    
    system("reset");
    printf("Welcome to netchat!\n\n");
    while ( 1 ) {
        printf("\n%s\n", get_cur_time(timebuf, BUFF_SIZE));
        fflush(stdin);
        memset(buffer, 0, BUFF_SIZE);
        fgets(buffer, BUFF_SIZE, fp);   // read a line from stdin
        if ( strcmp(buffer, "-q") == 0 ) {
    	    return (void *)EXIT;
    	} else if ( strcmp(buffer, "-logout") == 0 ) {
    	    return (void *)LOGOUT;
    	}
    	send_message(buffer, strlen(buffer));
    }
    
    fclose(fp);
    pthread_exit(NULL);
}

static char *get_cur_time(char *buf, int size)
{
    time_t t;
	struct tm tm;
	
	if ( size <= 20) {
	    printf("Stored time buffer is not big enough!\n");
	    return NULL;
	}
	
	memset(buf, 0, size);
	t = time(NULL);
	gmtime_r(&t, &tm);
	sprintf(buf, "%04d-%02d-%02d %02d:%02d:%02d", tm.tm_year+1900, tm.tm_mon, tm.tm_mday
	                                , tm.tm_hour+8, tm.tm_min, tm.tm_sec);
	
	// used for testing time value
	//printf("result: %s\n", buf);
	
	return buf;
}

